package cz.matasek.dbLogger.model;

import cz.matasek.dbLogger.LoggingAvailability;
import cz.matasek.dbLogger.Util;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * The class represents a database table.
 *
 * @author Luboš Matásek
 */
public final class Table implements Comparable<Table> {

    /**
     * Name of the table.
     */
    private String name;
    /**
     * Mapping of column names to column objects. Contains all columns present
     * in the table.
     */
    private Map<String, Column> columns;
    /**
     * <code>true</code> if changes in this table are logged.
     */
    private boolean isLogged = false;
    /**
     * Events, which fires the logging.
     */
    private TriggeringEvents loggedEvents;
    /**
     * <code>true</code> if this table has a history table.
     */
    private boolean hasHistoryTable = false;
    /**
     * The database, table is stored in.
     */
    private Database database;

    /**
     * Creates a new <code>Table</code> with the specified name and columns.
     *
     * @param database the database, table is stored in
     * @param name name of the table
     * @param columns mapping of column names to column data types
     */
    public Table(Database database, String name, Map<String, String> columns) {
        this.database = database;
        this.name = name;

        this.columns = new HashMap<String, Column>();
        for (Entry<String, String> column : columns.entrySet()) {
            String columnName = column.getKey();
            String columnDataType = column.getValue();
            this.columns.put(columnName, new Column(columnName, columnDataType));
        }
    }

    /**
     * Returns the name of the table.
     *
     * @return the name of the table
     */
    public String getName() {
        return name;
    }

    /**
     * Returns the string representation of this table.
     *
     * @return the name of the table
     */
    @Override
    public String toString() {
        return this.name;
    }

    /**
     * Returns the collection of columns present in this table.
     *
     * @return the collection of columns in the table
     */
    public Collection<Column> getColumns() {
        return columns.values();
    }

    /**
     * Returns the collection of columns, which were not auto-generated by the
     * logging application.
     *
     * @return the collection of non-logging columns
     */
    public Collection<Column> getNonLoggingColumns() {
        if (database.getLoggingAvailability() == LoggingAvailability.UNAVAILABLE) {
            return getColumns();
        }

        Set<Column> nonLoggingColumns = new HashSet<Column>();
        String loggingPrefix = database.getLoggingPrefix();

        for (Column column : columns.values()) {
            if (!column.isLoggingColumn(loggingPrefix)) {
                nonLoggingColumns.add(column);
            }
        }

        return nonLoggingColumns;
    }

    /**
     * Returns number of columns in this table.
     *
     * @return number of columns in the table
     */
    public int columnsCount() {
        return columns.size();
    }

    /**
     * Indicates whether some other object is "equal to" this table. Object is
     * equal only if:
     * <ul>
     *  <li>it is an instance of the class <code>Table</code></li>
     *  <li>names of both tables are the same</li>
     * </ul>
     * 
     * @param o the reference object with which to compare
     * @return <code>true</code> if this object is the same as the obj argument;
     * <code>false</code> otherwise
     */
    @Override
    public boolean equals(Object o) {
        if (o == null || !(o instanceof Table)) {
            return false;
        }

        return name.equalsIgnoreCase(((Table) o).name);
    }

    /**
     * Returns a hash code value for this object.
     * 
     * @return hash code value for this object
     */
    @Override
    public int hashCode() {
        int hash = 5;
        return 29 * hash + (name != null ? name.toLowerCase().hashCode() : 0);
    }

    /**
     * Compares this table with the specified table for order. Table are ordered
     * alphabetically according to their names.
     * 
     * @param o the table to be compared
     * @return a negative integer, zero, or a positive integer as this table is
     * less than, equal to, or greater than the specified table
     */
    @Override
    public int compareTo(Table o) {
        return name.compareToIgnoreCase(o.name);
    }

    /**
     * Retrieves if changes in this table are logged.
     *
     * @return <code>true</code> if changes are logged; <code>false</code>
     * otherwise
     */
    public boolean isLogged() {
        return isLogged;
    }

    /**
     * Sets if changes in this table are logged.
     *
     * @param isLogged <code>true</code> if changes are logged; 
     * <code>false</code> otherwise
     */
    public void setLogged(boolean isLogged) {
        this.isLogged = isLogged;
    }

    /**
     * Retrieves if a history table for this table exists.
     * 
     * @return <code>true</code> if history table exists; <code>false</code>
     * otherwise
     */
    public boolean hasHistoryTable() {
        return hasHistoryTable;
    }

    /**
     * Sets if a history table for this table exists.
     *
     * @param hasHistoryTable <code>true</code> if history table exists;
     * <code>false</code> otherwise
     */
    public void setHistoryTable(boolean hasHistoryTable) {
        this.hasHistoryTable = hasHistoryTable;
    }

    /**
     * Returns events, which fires logging in this table.
     *
     * @return events, which fires logging in this table
     */
    public TriggeringEvents getLoggedEvents() {
        return loggedEvents;
    }

    /**
     * Sets events, which fires logging in this table.
     *
     * @param loggedEvents events, which fires logging in this table
     */
    public void setLoggedEvents(TriggeringEvents loggedEvents) {
        this.loggedEvents = loggedEvents;
    }

    /**
     * Retrieves if this table is auto-generated by the logging application.
     * Generated tables begins with the logging application prefix (this prefix
     * is common for all logging objects in the database).
     *
     * @param prefix the prefix of all logging objects
     * @return <code>true</code> if this table is auto-generated by the logging
     * application; <code>false</code> otherwise
     */
    public boolean isLoggingTable(String prefix) {
        return Util.startsWithIgnoreCase(name, prefix);
    }

    /**
     * Sets columns, which are logged.
     *
     * @param loggedColumns collection of logged columns
     */
    public void setLoggedColumns(Collection<String> loggedColumns) {
        for (String columnName : loggedColumns) {
            Column column = columns.get(columnName);

            if (column != null) {
                column.setLogged(true);
            }
        }
    }
}
